package com.kjc.demo.basic.service;

import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * @author ：keep
 * @version ：
 * @date ：Created in 18:34 2019/4/28
 * @description：
 * @modified by：
 */
@Slf4j
@Service
public class RedisService {

    private static final String NOTICE_ORDER_CONTAINER = "NOTICE_ORDER_CONTAINER:";

    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    private static final Long SUCCESS = 1L;

    /**
     * 设置值，不设置过期时间
     */
    public void set(String key, Object value) {
        try {
            redisTemplate.opsForValue().set(key, value);
        } catch (Exception e) {
            log.error("redis设置key {} 失败:", key, e);
        }
    }

    /**
     * 设置值，设置过期时间
     */
    public void set(String key, Object value, long timeout, TimeUnit unit) {
        try {
            redisTemplate.opsForValue().set(key, value, timeout, unit);
        } catch (Exception e) {
            log.error("redis设置key {} 失败:", key, e);
        }
    }

    public void delete(String key) {
        try {
            redisTemplate.delete(key);
        } catch (Exception e) {
            log.error("redis delete key {} 失败:", key, e);
        }

    }

    /**
     * 如果不存在则设置值
     */
    public boolean setIfAbsent(String key, Object value, long timeout, TimeUnit unit) {
        boolean result = redisTemplate.opsForValue().setIfAbsent(key, value, timeout, unit);
        return result;
    }

    /**
     * 获取锁
     */
    public boolean getLock(String key, String value, int expireTime) {
        String script = "if redis.call('setNX',KEYS[1],ARGV[1]) == 1 then if redis.call('get',KEYS[1])==ARGV[1] then return redis.call('expire',KEYS[1],ARGV[2]) else return 0 end end";

        RedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class);
        Long result = redisTemplate.execute(redisScript, Lists.newArrayList(key), value, expireTime);

        return SUCCESS.equals(result);
    }

    /**
     * 解锁
     */
    public boolean releaseLock(String key, String value) {
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";

        RedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class);
        Long result = redisTemplate.execute(redisScript, Lists.newArrayList(key), value);

        return SUCCESS.equals(result);
    }

    /**
     * 自增值，不设置过期时间
     */
    public long increment(String key, long delta) {
        return redisTemplate.opsForValue().increment(key, delta);
    }

    /**
     * 自增值，设置过期时间
     */
    public long increment(String key, long delta, long timeout, TimeUnit unit) {
        long result = redisTemplate.opsForValue().increment(key, delta);

        //首次自增
        if (result == delta) {
            redisTemplate.expire(key, timeout, unit);
        }

        return result;
    }

    /**
     * 同时设置多个值，不设置过期时间
     */
    public void multiSet(Map<String, Object> map) {
        redisTemplate.opsForValue().multiSet(map);
    }

    /**
     * 同时设置多个值，设置过期时间
     */
    public void multiSet(Map<String, Object> map, long timeout, TimeUnit unit) {
        redisTemplate.opsForValue().multiSet(map);
        for (String key : map.keySet()) {
            redisTemplate.expire(key, timeout, unit);
        }
    }

    /**
     * 获取指定key的值
     */
    public <T> T get(String key) {
        try {
            return (T) redisTemplate.opsForValue().get(key);
        } catch (Exception e) {
            log.error("redis获取key:{}失败:", key, e);
            return null;
        }
    }

    /**
     * 列表左边push
     */
    public void leftPush(String key, Object value) {
        redisTemplate.opsForList().leftPush(key, value);
    }

    /**
     * 列表左边批量push
     */
    public void leftPushAll(String key, Collection<Object> values) {
        redisTemplate.opsForList().leftPushAll(key, values);
    }

    /**
     * 获取列表长度
     */
    public long size(String key) {
        return redisTemplate.opsForList().size(key);
    }

    /**
     * 列表右边pop
     */
    public <T> T rightPop(String key) {
        return (T) redisTemplate.opsForList().rightPop(key);
    }

    /**
     * 布隆过滤器插入
     *
     * @param key
     * @param value
     * @return
     */
    public Long bloomAdd(String key, String value) {
        long start = System.currentTimeMillis();
        // lua数组下标从1开始
        String script = "return redis.call('bf.add', KEYS[1], ARGV[1])";
        RedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class);
        Long result = (Long) redisTemplate.execute(redisScript, Lists.newArrayList(key), value);
        long end = System.currentTimeMillis();
        log.info("bf add {} - {} coast {} ms， result:{}", key, value, end - start, result);
        return result;
    }

    /**
     * 布隆过滤器是否存在
     *
     * @param key
     * @param value
     * @return
     */
    public Long bloomExists(String key, String value) {
        long start = System.currentTimeMillis();
        // lua数组下标从1开始
        String script = "return redis.call('bf.exists', KEYS[1], ARGV[1])";
        RedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class);
        Long result = (Long) redisTemplate.execute(redisScript, Lists.newArrayList(key), value);
        long end = System.currentTimeMillis();
        log.info("bf exists {} - {} coast {} ms， result:{}", key, value, end - start, result);
        return result;
    }

    /**
     * 向set集合中添加元素value
     * key已存在：0
     * key不存在：1
     */
    public Long addForSet(String key, String value) {
        Long result = redisTemplate.opsForSet().add(key, value);
        return result;
    }

    /**
     * 判断 value元素是否是集合 key 的成员
     */
    public Boolean isMemberForSet(String key, String value) {
        Boolean result = redisTemplate.opsForSet().isMember(key, value);
        return result;
    }

    /**
     * 通知容器（用于处理重复通知判断）
     */
    public void addNoticeContainer(String key) {
        try {
            String fullKey = NOTICE_ORDER_CONTAINER + key;
            redisTemplate.opsForValue().set(fullKey, true, 1, TimeUnit.DAYS);
        } catch (Exception e) {
            log.error("缓存通知key失败", e);
        }
    }

    /**
     * 判断是否已通知
     */
    public Boolean isNoticeContainer(String key) {
        String fullKey = NOTICE_ORDER_CONTAINER + key;
        Object result = redisTemplate.opsForValue().get(fullKey);
        return (result != null);
    }
}
